<?

class Bibtex
{

	private $fields;

	public function __construct()
	{
		$this->initialize();
	}

	public function getFields($type='')
	{
		if ($type == '')
		{
			return $this->fields;
		}
		else
		{
			foreach ($this->fields as $v)
			{
				if ($v['name'] == $type)
				{
					return $v;
				}
			}
		}
	}

	public function getTypes()
	{
		$t = array();
		foreach($this->fields as $f)
		{
			$t[] = $f['name'];
		}
		return $t;
	}
	
	public function getFilename($entryID)
	{
        clearstatcache();	
        global $rootdir;
        $files = glob($rootdir.'uploads/'.$entryID.'.*');
        if (count($files) == 1)
        {
            $filename = pathinfo($files[0]);
            return $filename['basename'];
        }	
        return false;
	}

    public function getPreview($entryID)
    {
        global $rootdir;
        $files = glob($rootdir.'uploads/'.$entryID.'_preview.png');
        if (count($files) == 1)
        {
            $preview = pathinfo($files[0]);
            return array($entryID.'_tn_preview.png', $entryID.'_preview.png');
        }

        // No preview found.  Can we make one?
        if ($this->getFilename($entryID) !== false)
        {
            $this->makePreview($entryID);
            $files = glob($rootdir.'uploads/'.$entryID.'_preview.png');
            if (count($files) == 1)
            {
                $preview = pathinfo($files[0]);
                return array($entryID.'_tn_preview.png', $entryID.'_preview.png');
            }
        }

        return false;
    }

    public function makePreview($entryID)
    {
        if ($this->getFilename($entryID) !== false)
        {
            global $rootdir;
            $pdf = $rootdir.'uploads/'.$this->getFilename($entryID);
            $png = $rootdir.'uploads/'.$entryID.'_preview.png';
            $tnpng = $rootdir.'uploads/'.$entryID.'_tn_preview.png';
            $entry = $this->getEntry($entryID, true);
            $page = $entry['previewPage'];
            if ($page == 0) $page = 1;

            // Use GS to convert PDF into PNG
            $cmd = 'gs -q -sDEVICE=pngmono -dBATCH -dNOPAUSE -dFirstPage='.$page.' -dLastPage='.$page.' -r300 -sOutputFile='.$png.' '.$pdf;
            exec($cmd);

            // Make thumbnail
            $cmd = 'convert "'.$png.'" -resize 200 "'.$tnpng.'"';
            exec($cmd);

            // Use ImageMagick to resize
            $cmd = 'convert "'.$png.'" -resize 550 "'.$png.'"';
            exec($cmd);

        }
    }
	
    public function suppressWarnings($ID, $suppress)
    {
        global $db;
        global $user;

        if ($ID != '' && $user->getID() != 0)
        {
            $sql = "UPDATE entries SET suppressWarnings=:suppressWarnings WHERE ID=:ID";
            $query = $db->prepare($sql);
            $values = array("suppressWarnings"=>0, "ID"=>$ID);
            if ($suppress) $values["suppressWarnings"] = 1;
            $query->execute($values);
        }
    }

	public function saveEntry($ID, $entry)
	{
		global $db;
        global $user;
		
		$values = '';
		
		if ($ID != '')
		{
		  $sql = "UPDATE entries SET ";
		  
		  $old = $this->getEntry($ID, true);
		  foreach($old as $k=>$v)
		  {
		      if (isset($entry[$k]) && $entry[$k] != $v)
		      {
		          $this->record(array('entryID'=>$ID, 'user'=>$user->getID(), 'field'=>$k, 'old'=>$v, 'new'=>$entry[$k]));
		      }
		  }
		  
        }
        else
        {
            $sql = "INSERT INTO entries SET ";
            $entry['user'] = $user->getID();
        }
		foreach($entry as $k=>$v)
		{
			$sql .= $k.'=:'.$k.', ';
		}
		$sql = substr($sql, 0, -2);
		if ($ID != '')
		{
            $sql .= " WHERE ID=:ID";
            $entry['ID'] = $ID;
        }		
        else
        {
            $sql .= ", added=NOW()";
        }
		$query = $db->prepare($sql);
		
		try {
			$query->execute($entry);
			return true;
		}
		catch (PDOException $e)
		{
			return $e->getMessage();
		}
	}
	
	public function getKeyID($key)
	{
        global $db;
        $sql = 'SELECT ID FROM entries WHERE entrykey=:entrykey';
        $query = $db->prepare($sql);
        $query->execute(array(':entrykey'=>$key));
        $entry = $query->fetch(PDO::FETCH_ASSOC);
        $ID = false;
        if ($entry !== false)
        {
            $ID = $entry['ID'];
        }
        return $ID;
	}
	
	public function duplicateSearch($entry)
	{
	   global $db;
	   $sql = 'SELECT * FROM entries';
	   $query = $db->prepare($sql);
	   $query->execute();
	   $e = $query->fetchAll(PDO::FETCH_ASSOC);
      
       foreach($e as $v)
       {
           $titleScore = levenshtein(strtolower($v['title']), strtolower($entry['title'])) / max(strlen($v['title']), strlen($entry['title']));
           
           if ($titleScore < 0.2 || ($v['volumne'] != '' && $v['number'] != '' && $v['pages'] != '' && $v['volume'] == $entry['volume'] && $v['number'] == $entry['number'] && $v['pages'] == $entry['pages']))
           {
                return $v;
           }
       }
       return false;
	}
	
	public function getEntry($ID, $show_latex)
	{
	   global $db;
	   $sql = 'SELECT * FROM entries WHERE ID=:ID';
	   $query = $db->prepare($sql);
	   $query->execute(array('ID'=>$ID));
	   $entry = $query->fetch(PDO::FETCH_ASSOC);
       array_walk($entry, array($this,"replaceCodes"), $show_latex);
	   return $entry;
	}
	
	public function getHistory($ID)
	{
	   global $db;
	   $sql = 'SELECT * FROM history WHERE entryID=:entryID ORDER BY timestamp ASC';
	   $query = $db->prepare($sql);
	   $query->execute(array(':entryID'=>$ID));
	   $history = $query->fetchAll(PDO::FETCH_ASSOC);
	   return $history;
	}
	
	public function search($q='', $num=999999, $sort_alph=true)
	{
	   global $db;
	   global $user;
	   
	   $fields = '';
	   $inputQuery = $q;
	   if (strpos($q, ':') !== false)
	   {
	       $code = substr($q, 0, strpos($q, ':'));
	       $q = substr($q, strpos($q, ':')+1);
	   }
	   else
	   {
	       $code = '';
	   }   
   
	   switch($code)
	   {
	       case 'meta':
	           $sql = 'SELECT *, (SELECT value FROM metadata WHERE entryID=entries.ID AND userID=:userID AND fieldID = (SELECT ID FROM metafields WHERE name=:name)) AS meta 
					   FROM entries 
					   WHERE ID IN (SELECT entryID FROM metadata WHERE fieldID IN (SELECT ID FROM metafields WHERE name=:name)) 
					   ORDER BY ID DESC';
	           $query = $db->prepare($sql);
			   $values = array(':name'=>$q, ':userID'=>$user->getID());
	           $query->execute($values);
               $found = $query->fetchAll(PDO::FETCH_ASSOC);                
	       break;
	       
	       case 'all':
           case 'allbibtex':
               if ($sort_auth)
               {
                   $sql = 'SELECT * FROM entries ORDER BY author LIMIT '.$num;
               } else {
                   $sql = 'SELECT * FROM entries ORDER BY ID DESC LIMIT '.$num;
               }
               $query = $db->prepare($sql);
               $query->execute();
               $found = $query->fetchAll(PDO::FETCH_ASSOC);
           break;
	              
            default:
                $fields = array('address','author','booktitle','editor','eprint','howpublished','institution','journal','note','organization','publisher','school','title','abstract', 'series', 'entrykey');
				$sql = 'SELECT DISTINCT E.* FROM entries AS E, metadata AS M 
						WHERE 
							  (M.entryID = E.ID AND  M.userID=:userID AND M.value LIKE :wquery) 
							  
							  OR
							  
							  MATCH(';
                foreach($fields as $f)
                {
                    $sql .= 'E.'.$f.',';
                }
                $sql = substr($sql,0,-1);
                $sql .= ') AGAINST(:query)';

                
                $query = $db->prepare($sql);
                $query->execute(array(':query'=>$q, ':wquery'=>'%'.$q.'%', ':userID'=>$user->getID()));
                $found = $query->fetchAll(PDO::FETCH_ASSOC);
            
            break;
       }       
       $found = $this->addFilenames($found);
       if ($code != 'allbibtex') {
           foreach ($found as $k=>$v) {
               array_walk($found[$k], array($this,"replaceCodes"), false);
           }
       }
	   return array($this->addFilenames($found), array($code, $q), $inputQuery);
	}
	
	private function replaceCodes(&$input, $key, $show_latex)
	{
       $input = htmlspecialchars($input, ENT_NOQUOTES);

       if (!$show_latex)
       {
           // LaTeX Special Characters
           $input = str_replace('\"{a}', '&#228;', $input);
           $input = str_replace('\"{e}', '&#235;', $input);
           $input = str_replace('\"{i}', '&#239;', $input);
           $input = str_replace('\"{o}', '&#246;', $input);
           $input = str_replace('\"{u}', '&#252;', $input); 
           $input = str_replace('\"{y}', '&#255;', $input);
           $input = str_replace('\`{a}', '&#224;', $input);
           $input = str_replace('\`{e}', '&#232;', $input);
           $input = str_replace('\`{i}', '&#236;', $input);
           $input = str_replace('\`{o}', '&#242;', $input);
           $input = str_replace('\`{u}', '&#249;', $input); 
           $input = str_replace('\^{a}', '&#226;', $input);
           $input = str_replace('\^{e}', '&#234;', $input);
           $input = str_replace('\^{i}', '&#238;', $input);
           $input = str_replace('\^{o}', '&#244;', $input);
           $input = str_replace('\^{u}', '&#251;', $input); 
           $input = str_replace('\\\'{a}', '&#225;', $input);
           $input = str_replace('\\\'{e}', '&#233;', $input);
           $input = str_replace('\\\'{i}', '&#237;', $input);
           $input = str_replace('\\\'{o}', '&#243;', $input);
           $input = str_replace('\\\'{u}', '&#250;', $input); 
           $input = str_replace('\\\'{y}', '&#253;', $input);
           $input = str_replace('\~{a}', '&#227;', $input); 
           $input = str_replace('\~{n}', '&#241;', $input); 
           $input = str_replace('\~{o}', '&#240;', $input); 
           $input = str_replace('{\o}', '&#248;', $input); 

           $input = str_replace('\"{A}', '&#196;', $input);
           $input = str_replace('\"{E}', '&#203;', $input);
           $input = str_replace('\"{I}', '&#207;', $input);
           $input = str_replace('\"{O}', '&#214;', $input);
           $input = str_replace('\"{U}', '&#220;', $input); 
           $input = str_replace('\`{A}', '&#192;', $input);
           $input = str_replace('\`{E}', '&#200;', $input);
           $input = str_replace('\`{I}', '&#204;', $input);
           $input = str_replace('\`{O}', '&#210;', $input);
           $input = str_replace('\`{U}', '&#217;', $input); 
           $input = str_replace('\^{A}', '&#194;', $input);
           $input = str_replace('\^{E}', '&#202;', $input);
           $input = str_replace('\^{I}', '&#206;', $input);
           $input = str_replace('\^{O}', '&#212;', $input);
           $input = str_replace('\^{U}', '&#219;', $input); 
           $input = str_replace('\\\'{A}', '&#193;', $input);
           $input = str_replace('\\\'{E}', '&#201;', $input);
           $input = str_replace('\\\'{I}', '&#205;', $input);
           $input = str_replace('\\\'{O}', '&#211;', $input);
           $input = str_replace('\\\'{U}', '&#218;', $input); 
           $input = str_replace('\\\'{Y}', '&#221;', $input);
           $input = str_replace('\~{A}', '&#195;', $input); 
           $input = str_replace('\~{N}', '&#209;', $input); 
           $input = str_replace('\~{O}', '&#213;', $input); 

           // LaTeX Modifies Style
           // Regex recurrsion would be hard, but I think PHP can do it with the ?R parameter or something
           // I just do it manually here.  The strings are not very long.
           
           while (strpos($input, "$") !== false)
           {
               $pos = strpos($input, "$");
               for($i = $pos+1; $i < strlen($input); $i++)
               {
                   if ($input[$i] == "$") break;
               }
               $input = substr_replace($input, '</math>', $i, 1); // Replace closing brace
               $input = substr_replace($input, '<math>', $pos, 1); // Replace "\emph{"
           }

           while (strpos($input, "\\emph") !== false)
           {
               $pos = strpos($input, "\\emph");
               $open = 0;
               for($i = $pos+5; $i < strlen($input); $i++)
               {
                   if ($input[$i] == '{') $open++;
                   if ($input[$i] == '}') $open--;
                   if ($open == 0) break;
               }
               if ($open == 0) // Make sure we are balanced and not just ill-formed.
               {
                    $input = substr_replace($input, '</em>', $i, 1); // Replace closing brace
                    $input = substr_replace($input, '<em>', $pos, 6); // Replace "\emph{"
               }
           }
           // Remove just plain old { }'s
           while (strpos($input, "{") !== false)
           {
               $pos = strpos($input, "{");
               $open = 0;
               for($i = $pos; $i < strlen($input); $i++)
               {
                   if ($input[$i] == '{') $open++;
                   if ($input[$i] == '}') $open--;
                   if ($open == 0) break;
               }
               if ($open == 0) // Make sure we are balanced and not just ill-formed.
               {
                    $input = substr_replace($input, '</strong>', $i, 1); // Replace closing brace
                    $input = substr_replace($input, '<strong>', $pos, 1); // Replace "\emph{"
               }
           }


       }

       // Now that the LaTeX stuff is out of the way
       $input = str_replace('"', '&quot;', $input);
	}

	private function addFilenames($items)
	{
	   foreach ($items as $k=>$v)
	   {
	       $items[$k]['filename'] = $this->getFilename($v['ID']);
	   }
	   return $items;
	}

	public function getMetaSearch($q, $ID, $userID)
	{
	   global $db;
	   $sql = 'SELECT value FROM metadata WHERE field = (SELECT field FORM metadata WHERE ID = :ID) AND userID=:userID';
	   $query = $db->prepare($sql);
	   $query->execute(array(':ID'=>$ID, ':userID'=>$userID));
	   $values = $query->fetchAll(PDO::FETCH_ASSOC);
	   $r = array();
	   foreach($values as $v)
	   {
	       if (stripos($v['value'], $q))
	       {
	           $r[] = $v['value'];
	       }
	   }
	   return $r;
	}	
	
	public function getMetaFields($userID)
	{
        global $db;        
		$sql = 'SELECT * FROM metafields WHERE userID=:userID OR userID=0';
        $query = $db->prepare($sql);
        $query->execute(array(':userID'=>$userID));
        $fields = $query->fetchAll(PDO::FETCH_ASSOC);
        
        foreach($fields as $k=>$v)
        {
            if ($v['type'] == 'il')
            {
                $fields[$k]['items'] = explode(",", $v['items']);
            }
        }
        
        return $fields;
	}
	
	public function getMetaValues($userID, $entryID)
	{
	   global $db;
	   $sql = 'SELECT metadata.*, metafields.type as type FROM metadata, metafields WHERE metafields.ID = metadata.fieldID AND metadata.userID=:userID AND metadata.entryID=:entryID';
	   $query = $db->prepare($sql);
	   $query->execute(array('userID'=>$userID, 'entryID'=>$entryID));
	   $r = $query->fetchAll(PDO::FETCH_ASSOC);
	   $values = array();
	   foreach($r as $v)
	   {
	       $values[$v['fieldID']] = $v['value'];
	       
	       if ($v['type'] == 'kw')
	       {
	           $values[$v['fieldID']] = explode(',', $v['value']);
	       }
	       
	   }
	   return $values;
	}

	
	public function saveMeta($userID, $entryID, $fieldID, $value)
	{
	   global $db;
	   if (is_array($value))
	   {
	       $value = implode(",", $value);
	   }
	   if ($value != '')
	   {
            $sql = 'REPLACE metadata SET userID=:userID, entryID=:entryID, fieldID=:fieldID, value=:value';
            $query = $db->prepare($sql);
            $query->execute(array('userID'=>$userID, 'entryID'=>$entryID, 'fieldID'=>$fieldID, 'value'=>$value));
	   }
	   else
	   {
	       $sql = 'DELETE FROM metadata WHERE userID=:userID AND entryID=:entryID AND fieldID=:fieldID';
	       $query = $db->prepare($sql);
           $query->execute(array('userID'=>$userID, 'entryID'=>$entryID, 'fieldID'=>$fieldID));
	   }
	}


	
	private function record($values)
	{
	   global $db;
	   $sql = 'INSERT INTO history SET entryID=:entryID, user=:user, field=:field, old=:old, new=:new';
	   $query = $db->prepare($sql);
	   $query->execute($values);
	}
	
	private function initialize()
	{
		$this->fields = array(
			array('name'=>'article',
 				  'req'=>array('author','title','journal','year'),
 				  'opt'=>array('volume','number','pages','month','note')),
			array('name'=>'book',
				  'req'=>array('author','title','publisher','year'),							 
				  'opt'=>array('volume','series','address','edition','month','note')),
			array('name'=>'inbook',
				  'req'=>array('author','title','booktitle','publisher','year'),							 
				  'opt'=>array('chapter','pages','volume','editor','number','series','type','address','edition','month','note')),
			array('name'=>'incollection',
				  'req'=>array('author','title','booktitle','year'),							 
				  'opt'=>array('chapter','pages','volume','publisher','editor','number','series','type','address','edition','month','note')),
            array('name'=>'inproceedings',
                  'req'=>array('author','title','booktitle','year'),
                  'opt'=>array('editor','pages','organization','publisher','address','month','note')),
			array('name'=>'unpublished',
				  'req'=>array('author','title','note'),							 
				  'opt'=>array('month','year'))
		);
	}
}

